package org.tgit.gateway.filter;


import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cache.Cache;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.tgit.gateway.common.ApiConstants;
import org.tgit.gateway.common.ApiMonitorData;
import org.tgit.gateway.common.CommonUtil;
import org.tgit.gateway.common.DESUtils;
import org.tgit.gateway.common.MD5Utils;
import org.tgit.gateway.config.AsyncLogConfig;
import org.tgit.gateway.service.GatewayService;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 
 * @ClassName: ReqPreHandlerFilter
 * @Description: TODO(认证授权过滤器-全局，局部是实现GatewayFilter)
 * @author fanzaijun
 * @date 2018年8月13日 下午12:45:20
 *
 */
@Component
public class CertificateAuthorityFilter implements GlobalFilter, Ordered ,CommandLineRunner{

	@Autowired
	AsyncLogConfig asyncLogConfig;
	
	@Autowired
	RedisTemplate redisTemplate;
	
	@Autowired
	LoadBalancerClient loadBalancer;
	
	@Autowired
	GatewayService gatewayService;
	
	@Resource(name = "gatewayCache")
	Cache  gatewayCache;
	
	public static Logger LOGGER = LoggerFactory.getLogger(CertificateAuthorityFilter.class);
	
	/**
	 * 网关启动后执行的业务
	 * @param args
	 * @throws Exception  
	 * @see org.springframework.boot.CommandLineRunner#run(java.lang.String[])
	 */
	@Override
	public void run(String... args) throws Exception {
		gatewayService.reloadApiGateWayData();
	}
	
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		final ApiMonitorData apiMonitorData=new ApiMonitorData();
		try {
			apiMonitorData.setBeginAccTime(System.currentTimeMillis());
			ServerHttpRequest req=exchange.getRequest();
			//请求URI获取
			String reqUri = req.getPath().pathWithinApplication().value()== null ? ApiConstants.NULL_CHANGE : req.getPath().pathWithinApplication().value();
			//访问TOKEN,携带了appId和apiId信息
			String accessToken = req.getHeaders().get(ApiConstants.ACCESS_TOKEN) == null ? ApiConstants.NULL_CHANGE: req.getHeaders().get(ApiConstants.ACCESS_TOKEN).get(0);
			//reqIp
			String reqIp=req.getURI().getHost();
			//第一步从accessToken解析出appId,apiId
			if(StringUtils.isEmpty(accessToken)) {
				//REQ_ERROR_CODE_100错误
				apiMonitorData.setAccessUri(reqUri);
				apiMonitorData.setAccessToken(accessToken);
				apiMonitorData.setLogId(CommonUtil.genLogid());
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setAppCode("");
				apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_404);
				apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_108);
				return errorForwardFallBack(exchange,apiMonitorData,chain);
			}
			//从请求头获取apiId
			String appId = DESUtils.decode(accessToken).split("\\$")[0];
			String apiId = DESUtils.decode(accessToken).split("\\$")[1];
			//通过请求URI获取缓存加密访问URI格式
			String accessUri = ApiConstants.GATEWAY_PRE_KEY + MD5Utils.md5(reqUri+ApiConstants.SYMBOL+apiId);
			//请求时开始时间--chain.filter(exchange)之前的就是 “pre” 部分，后的也就是then里边的是 “post” 部分。
			// 第二步:判断访问uri是否存在
			if(null==gatewayCache.get(accessUri)) {
				//REQ_ERROR_CODE_100错误
				apiMonitorData.setAccessUri(accessUri);
				apiMonitorData.setAccessToken(accessToken);
				apiMonitorData.setLogId(CommonUtil.genLogid());
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setApiId(apiId);
				apiMonitorData.setAppCode(appId);
				apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_404);
				apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_100);
				return errorForwardFallBack(exchange,apiMonitorData,chain);
			}
			apiMonitorData.setAccessUri(reqUri);
			// 第三步:网关自身限流,已处理-见NetLimitGatewayFilterFactory
			// 第四步 :查询API信息,API状态校验
			Map<String, Object> apiInfo = (Map<String, Object>) gatewayCache.get(accessUri).get();
			//api状态
			String apiStatus = !apiInfo.isEmpty() && null != apiInfo.get(ApiConstants.STATUS) ? apiInfo.get(ApiConstants.STATUS).toString() : null;
			//apiId
			String apiId_k = !apiInfo.isEmpty() && null != apiInfo.get(ApiConstants.API_ID) ? apiInfo.get(ApiConstants.API_ID).toString() : null;
			//获取部署URI
			String deployUri=!apiInfo.isEmpty() && null != apiInfo.get(ApiConstants.DEPLOY_URI) ? apiInfo.get(ApiConstants.DEPLOY_URI).toString() : null;
			//嵌入日志对象
			apiMonitorData.setApiId(apiId_k);
			// api信息校验
			// 0-allow; 1-close;2-overdue;3-disabled;4-all servers is not exists!
			if(!StringUtils.isEmpty(apiStatus) && !ApiConstants.ZERO.equals(apiStatus)){
				apiMonitorData.setEndAccTime(System.currentTimeMillis());
				apiMonitorData.setCostTime(apiMonitorData.getEndAccTime() - apiMonitorData.getBeginAccTime());
				apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_405);
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setApiId(apiId);
				apiMonitorData.setAppCode(appId);
				switch (apiStatus) {
				case "1":
					apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_101);
					break;
				case "2":
					apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_102);
					break;
				case "3":
					apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_103);
					break;
				case "4":
					apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_104);
					break;
				default:
					break;
				}
				return errorForwardFallBack(exchange,apiMonitorData,chain);
			}
			// 第五步：获取API标识
			if (StringUtils.isEmpty(apiId_k)) {
				apiMonitorData.setEndAccTime(System.currentTimeMillis());
				apiMonitorData.setCostTime(apiMonitorData.getEndAccTime()- apiMonitorData.getBeginAccTime());
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setApiId(apiId);
				apiMonitorData.setAppCode(appId);
				apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_105);
			}
			// 第六步：授权
			String authKey=CommonUtil.getAccessUriAppCodeAccessToken(reqUri,appId,accessToken);
			if(null==gatewayCache.get(authKey)) {
				//鉴权信息为空
				apiMonitorData.setAccessUri(accessUri);
				apiMonitorData.setAccessToken(accessToken);
				apiMonitorData.setLogId(CommonUtil.genLogid());
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setAppCode(appId);
				apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_404);
				apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_109);
				return errorForwardFallBack(exchange,apiMonitorData,chain);
			}else {
				String authCode=(String) gatewayCache.get(authKey).get();
				if(null==authCode || !MD5Utils.md5(reqUri).equals(authCode)) {
					//鉴权信息不匹配，伪造
					apiMonitorData.setAccessUri(accessUri);
					apiMonitorData.setAccessToken(accessToken);
					apiMonitorData.setLogId(CommonUtil.genLogid());
					apiMonitorData.setReqIp(reqIp);
					apiMonitorData.setAppCode(appId);
					apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_404);
					apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_109);
					return errorForwardFallBack(exchange,apiMonitorData,chain);
				}
			}
			//路由负载均衡策略之前，嵌入正确的URI
			ServerHttpRequest request = req.mutate()
					.path(deployUri)
					.build();
			return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(()->{
				apiMonitorData.setReqIp(reqIp);
				apiMonitorData.setApiId(apiId);
				apiMonitorData.setAppCode(appId);
				apiMonitorData.setAccessToken(accessToken);
				apiMonitorData.setReturnCode(ApiConstants.RETURN_CODE_DEFAULT);
				URI deployUri2=exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
				apiMonitorData.setRewriteDeployUrl(deployUri2.toString());
	        	apiMonitorData.setEndAccTime(System.currentTimeMillis());
	        	apiMonitorData.setLogId(CommonUtil.genLogid());
	        	apiMonitorData.setCostTime(apiMonitorData.getEndAccTime()-apiMonitorData.getBeginAccTime());
	        	asyncLogConfig.consoleLog(apiMonitorData);
	         }));
			
		}catch (Exception e) {
			apiMonitorData.setReqErrorCode(ApiConstants.REQ_ERROR_CODE_112);
			return errorForwardFallBack(exchange,apiMonitorData,chain);
		}
	}
	
	/**
	 * 
	* @Title: errorFallBack   
	* @Description: 重定向方式
	* @author fanzaijun  
	* @date 2018年10月30日 上午10:49:29   
	* @param exchange
	* @param id
	* @param apiMonitorData
	* @return  
	* Mono<Void>  
	* @throws (没有异常抛出)
	 */
	public Mono<Void> errorDirectFallBack(ServerWebExchange exchange,String id,final ApiMonitorData apiMonitorData) {
		 ServerHttpResponse response = exchange.getResponse();
		 //路由转向303
         response.setStatusCode(HttpStatus.SEE_OTHER);
         //错误码
         response.getHeaders().set("Location", "/rest/gateway/manager/error/"+id);
         //设置请求完毕，转303
         return exchange.getResponse().setComplete().then(Mono.fromRunnable(()->{
        	 apiMonitorData.setEndAccTime(System.currentTimeMillis());
        	 apiMonitorData.setReqErrorCode(id);
        	 apiMonitorData.setCostTime(apiMonitorData.getEndAccTime()-apiMonitorData.getBeginAccTime());
         }));
	}
	/**
	 * 
	 * @Title: errorFallBack   
	 * @Description: 错误转发方式
	 * @author fanzaijun  
	 * @date 2018年10月30日 上午10:49:29   
	 * @param exchange
	 * @param id
	 * @param apiMonitorData
	 * @return  
	 * Mono<Void>  
	 * @throws (没有异常抛出)
	 */
	public Mono<Void> errorForwardFallBack(ServerWebExchange exchange,final ApiMonitorData apiMonitorData,GatewayFilterChain chain) {
		Route route=exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
		Route newRoute=Route.async()
				.asyncPredicate(route.getPredicate())
				.filters(route.getFilters())
				.id(route.getId())
				.order(route.getOrder())
				.uri("forward:///rest/gateway/manager/error/"+apiMonitorData.getReqErrorCode())
				.build();
		exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, newRoute);
	    return chain.filter(exchange).then(Mono.fromRunnable(()->{
        	 apiMonitorData.setEndAccTime(System.currentTimeMillis());
        	 apiMonitorData.setCostTime(apiMonitorData.getEndAccTime()-apiMonitorData.getBeginAccTime());
        	 asyncLogConfig.consoleLog(apiMonitorData);
         }));
	}
	 /**
     * 返回jwt中的用户信息
     *
     * @param request
     * @param ctx
     * @return
     */
    @SuppressWarnings("unused")
	private String getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
        List<String> strings = request.getHeaders().get("JWT_TOKEN");
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0);
        }
        if (StringUtils.isEmpty(authToken)) {
            strings = request.getQueryParams().get("token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }
        //
        return authToken;
    }

    /**
     * 网关抛异常
     *
     * @param body
     */
    @SuppressWarnings("unused")
	private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, Object map) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
//        byte[] bytes = JSONObject.toJSONString(map).getBytes(StandardCharsets.UTF_8);
        byte[] bytes =new byte[1024];
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

}
